#pragma once
#include "Runnable.h"
#include "RunnableThread.h"
#include "IThreadWork.h"
#include "ThreadPool.h"
#include "ThreadCounter.h"

/**
 * Stub class to use a base class for tasks that cannot be abandoned
 */
class NonAbandonableTask
{
public:
	virtual bool CanAbandon()
	{
		return false;
	}
	virtual void Abandon()
	{
	}
};


/**
	AutoDeleteAsyncTask - template task for jobs that delete themselves when complete

	Sample code:

	class ExampleAutoDeleteAsyncTask : public NonAbandonableTask
	{
		friend class FAutoDeleteAsyncTask<ExampleAutoDeleteAsyncTask>;

		int32 ExampleData;

		ExampleAutoDeleteAsyncTask(int32 InExampleData)
		 : ExampleData(InExampleData)
		{
		}

		void DoWork()
		{
			... do the work here
		}
	};


	void Example()
	{
		// start an example job
		(new FAutoDeleteAsyncTask<ExampleAutoDeleteAsyncTask>(5)->Start();
	}

**/

template<typename Task>
class AutoDeleteAsyncTask
	: private IThreadWork
{
	/** User job embedded in this task */
	Task mTask;

	/**
	 * Tells the user job to do the work, sometimes called synchronously, sometimes from the thread pool. Calls the event tracker.
	 **/
	void DoWork()
	{
		mTask.DoWork();
		delete this;
	}

	/**
	* Always called from the thread pool. Just passes off to DoWork
	**/
	virtual void DoThreadedWork() override
	{
		DoWork();
	}

	/**
	 * Always called from the thread pool. Called if the task is removed from queue before it has started which might happen at exit.
	 * If the user job can abandon, we do that, otherwise we force the work to be done now (doing nothing would not be safe).
	 */
	virtual void Abandon(void)
	{
		if (mTask.CanAbandon())
		{
			mTask.Abandon();
			delete this;
		}
		else
		{
			DoWork();
		}
	}

public:

	/** Forwarding constructor. */
	template<typename...T>
	explicit AutoDeleteAsyncTask(T&&... Args) : mTask(std::forward<T>(Args)...)
	{
	}

	/**
	* Run this task on this thread, now. Will end up destroying myself, so it is not safe to use this object after this call.
	**/
	void Start()
	{
		GThreadPool->AddThreadWork(this);
	}
};


/**
	AsyncTask - template task for jobs queued to thread pools

	Sample code:

	class ExampleAsyncTask : public FNonAbandonableTask
	{
		friend class ManualAsyncTask<ExampleAsyncTask>;

		int32 ExampleData;

		ExampleAsyncTask(int32 InExampleData)
		 : ExampleData(InExampleData)
		{
		}

		void DoWork()
		{
			... do the work here
		}
	};

	void Example()
	{

		//start an example job

		ManualAsyncTask<ExampleAsyncTask>* MyTask = new ManualAsyncTask<ExampleAsyncTask>( 5 );
		MyTask->Start();

		//to just do it now on this thread
		//Check if the task is done :

		if (MyTask->IsDone())
		{
		}

		//Spinning on IsDone is not acceptable( see EnsureCompletion ), but it is ok to check once a frame.
		//Ensure the task is done, doing the task on the current thread if it has not been started, waiting until completion in all cases.

		MyTask->EnsureCompletion();
		delete Task;
	}
**/


template<typename Task>
class ManualAsyncTask
	: private IThreadWork
{
	/** User job embedded in this task */
	Task mTask;
	/** Thread safe counter that indicates WORK completion, no necessarily finalization of the job */
	ThreadCounter mWorkNotFinishedCounter;
	/** Holds the handle to the event. */
	HANDLE mDoneEvent;

	/* Internal function to destroy the completion event
	**/
	void DestroyEvent()
	{
		CloseHandle(mDoneEvent);
		mDoneEvent = nullptr;
	}

	/**
	* Tells the user job to do the work, sometimes called synchronously, sometimes from the thread pool. Calls the event tracker.
	**/
	void DoWork()
	{
		mTask.DoWork();
		assert(mWorkNotFinishedCounter.GetValue() == 1);
		mWorkNotFinishedCounter.Decrement();
	}

	/**
	* Triggers the work completion event, only called from a pool thread
	**/
	void FinishThreadedWork()
	{
		if (mDoneEvent != nullptr)
		{
			// 使事件有信号量, 用于激活线程运行
			SetEvent(mDoneEvent);
		}
	}

	/**
	* Performs the work, this is only called from a pool thread.
	**/
	virtual void DoThreadedWork() override
	{
		DoWork();
		FinishThreadedWork();
	}

	/**
	 * Always called from the thread pool. Called if the task is removed from queue before it has started which might happen at exit.
	 * If the user job can abandon, we do that, otherwise we force the work to be done now (doing nothing would not be safe).
	 */
	virtual void Abandon(void) override
	{
		if (mTask.CanAbandon())
		{
			mTask.Abandon();
			assert(mWorkNotFinishedCounter.GetValue() == 1);
			mWorkNotFinishedCounter.Decrement();
		}
		else
		{
			DoWork();
		}
		FinishThreadedWork();
	}

	/**
	* Internal call to assert that we are idle
	**/
	void CheckIdle() const
	{
		assert(mWorkNotFinishedCounter.GetValue() == 0);
	}

	/**
	* Internal call to synchronize completion between threads, never called from a pool thread
	**/
	void SyncCompletion()
	{
		assert(mDoneEvent != nullptr); // if it is not done yet, we must have an event
		// 设置等待事件有信号再继续执行线程
		WaitForSingleObject(mDoneEvent, INFINITE);
		CheckIdle();
	}

	/**
	* Internal call to initialize internal variables
	**/
	void Init()
	{
		mDoneEvent = nullptr;
	}

public:
	ManualAsyncTask()
		: Task()
	{
		// This constructor shouldn't be necessary as the forwarding constructor should handle it, but
		// we are getting VC internal compiler errors on CIS when creating arrays of AsyncTask.

		Init();
	}

	/** Forwarding constructor. */
	template <typename Arg0Type, typename... ArgTypes>
	ManualAsyncTask(Arg0Type&& Arg0, ArgTypes&&... Args)
		: mTask(std::forward<Arg0Type>(Arg0), std::forward<ArgTypes>(Args)...)
	{
		Init();
	}

	/** Destructor, not legal when a task is in process */
	~ManualAsyncTask()
	{
		// destroying an unfinished task is a bug
		CheckIdle();
		DestroyEvent();
	}

	/* Retrieve embedded user job, not legal to call while a job is in process
	* @return reference to embedded user job
	**/
	Task& GetTask()
	{
		CheckIdle();  // can't modify a job without it being completed first
		return mTask;
	}

	/* Retrieve embedded user job, not legal to call while a job is in process
	* @return reference to embedded user job
	**/
	const Task& GetTask() const
	{
		CheckIdle();  // could be safe, but I won't allow it anyway because the data could be changed while it is being read
		return mTask;
	}

	/* Generic start function, not called directly
		* @param bForceSynchronous if true, this job will be started synchronously, now, on this thread
	**/
	void Start()
	{
		CheckIdle();  // can't start a job twice without it being completed first
		mWorkNotFinishedCounter.Increment();
		if (mDoneEvent == nullptr)
		{
			mDoneEvent = CreateEvent(nullptr, false, 0, nullptr);
		}
		// 使事件无信号
		ResetEvent(mDoneEvent);
		GThreadPool->AddThreadWork(this);
	}

	/**
	* Wait until the job is complete
	* @param bDoWorkOnThisThreadIfNotStarted if true and the work has not been started, retract the async task and do it now on this thread
	**/
	void EnsureCompletion(bool doWorkOnThisThreadIfNotStarted = true)
	{
		bool doSyncCompletion = true;
		if (doWorkOnThisThreadIfNotStarted)
		{
			if (GThreadPool->RetractThreadWork(this))
			{
				// we got the job back, so do the work now and no need to synchronize
				doSyncCompletion = false;
				DoWork();
				FinishThreadedWork();
			}
			else if (mWorkNotFinishedCounter.GetValue())  // in the synchronous case, if we haven't done it yet, do it now
			{
				DoWork();
			}
		}
		if (doSyncCompletion)
		{
			SyncCompletion();
		}
		CheckIdle(); // Must have had bDoWorkOnThisThreadIfNotStarted == false and needed it to be true for a synchronous job
	}

	/**
	* Cancel the task, if possible.
	* Note that this is different than abandoning (which is called by the thread pool at shutdown).
	* @return true if the task was canceled and is safe to delete. If it wasn't canceled, it may be done, but that isn't checked here.
	**/
	bool Cancel()
	{
		if (GThreadPool->RetractThreadWork(this))
		{
			assert(mWorkNotFinishedCounter.GetValue() == 1);
			mWorkNotFinishedCounter.Decrement();
			FinishThreadedWork();
			return true;
		}
		return false;
	}

	/**
	* Wait until the job is complete, up to a time limit
	* @param TimeLimitSeconds Must be positive, if you want to wait forever or poll, use a different call.
	* @return true if the task is completed
	**/
	bool WaitCompletionWithTimeout(float timeLimitSeconds)
	{
		assert(timeLimitSeconds > 0.0f);

		uint32 waitTime = uint32(timeLimitSeconds * 1000.0f) + 1;
		assert(waitTime != 0);

		assert(mDoneEvent != nullptr); // if it is not done yet, we must have an event
		if (WaitForSingleObject(mDoneEvent, waitTime) == WAIT_OBJECT_0)
		{
			CheckIdle();
			return true;
		}
		return false;
	}

	/** Returns true if the work and TASK has completed, false while it's still in progress.
	 * prior to returning true, it synchronizes so the task can be destroyed or reused
	 */
	bool IsDone()
	{
		if (!IsWorkDone())
		{
			return false;
		}
		SyncCompletion();
		return true;
	}

	/** Returns true if the work has completed, false while it's still in progress.
	 * This does not block and if true, you can use the results.
	 * But you can't destroy or reuse the task without IsDone() being true or EnsureCompletion()
	*/
	bool IsWorkDone() const
	{
		if (mWorkNotFinishedCounter.GetValue())
		{
			return false;
		}
		return true;
	}

	/** Returns true if the work has not been started or has been completed.
	 * NOT to be used for synchronization, but great for check()'s
	 */
	bool IsIdle() const
	{
		return mWorkNotFinishedCounter.GetValue() == 0;
	}
};